package com.afteryuan.server.support.impl;

import com.afteryuan.server.command.model.Pane;
import com.afteryuan.server.support.MovingSupport;
import com.afteryuan.server.support.Point;
import com.afteryuan.server.support.astar.AStar;
import com.afteryuan.server.support.astar.Constants;
import com.afteryuan.server.support.astar.Node;
import com.afteryuan.server.data.SceneLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;


/**
 * MovingSupportAStar 寻路算法的A星实现
 * <p/>
 * <p><a href="ResopnsePolicy.java.html"><i>View Source</i></a></p>
 *
 * @author <a href="mailto:afteryuan@afteryuan.com">$Author$</a>
 * @version $Revision$
 */

public class MovingSupportAStar implements MovingSupport {

    private static final Log log = LogFactory.getLog(MovingSupportAStar.class);
    public static final int OUTOFMAP = -2;

    public List<Pane> movingPath(String sceneId, Point start, Point target) {
        return pathFind(SceneLoader.sceneDataMap.get(sceneId), start, target);
    }

    public Pane[] movingPathArray(String sceneId, Point start, Point target) {
        List<Pane> movPanes = movingPath(sceneId,start,target);
        Pane[] ps = new Pane[movPanes.size()];
        return movPanes.toArray(ps);
    }

    /**
     * A星寻路算法获取路径
     *
     * @param map    地图数组
     * @param start  起始点
     * @param target 终点
     * @return List<Pane> 可行走的路径
     */
    private List<Pane> pathFind(int[][] map, Point start, Point target) {
        //当终点为不可行走状态时，通过特定算法找到距终点最近的可行走终点
        if (getPaneSingle(map, start.x, start.y) < 0) {
            log.warn("起点不在地图上或者起点为不可行走区域！起点：" + start);
            return new ArrayList<Pane>();
        }
        if (getPaneSingle(map, target.x, target.y) == Constants.SOLID) {
            Point temp = findPointCanMoveTo(map, target);
            if (temp != null) {
                target.x = temp.x;
                target.y = temp.y;
            } else {
                log.warn("can not find an useful point. target is " + target);
                return new ArrayList<Pane>();
            }
        }
        //使用A星算法获得路径Vector
        AStar a = new AStar(map, Constants.LANDFORM, start, target);
        Vector v = a.find();
        //转换为客户端需要的格式并返回
        List<Pane> list = new ArrayList<Pane>();
        if (v != null) {
            for (int i = 0; i < v.size() - 1; i++) {
                Node cNode = (Node) v.get(i);
                Node tNode = (Node) v.get(i + 1);
                Pane p = new Pane();
                p.direction = getDirection(cNode, tNode);
                int state = map[tNode.location.y][tNode.location.x];
                p.depth = state;
                list.add(p);
            }
        }
        return list;
    }

    /**
     * 当终点为不可行走状态时，通过该方法获取距终点最近的一个可行走坐标点<br/>
     * 目前实现方法是：从终点向8个方向引射线。这样比以终点为圆心获得需要检查的
     * 范围来说，将大大减少范围，但表现将会有一些不合常理的时候。<br/>
     * 每一次循环检查8个格子，这8个格子的顺序为：左上、正上、右上、左、右、左下、正下、右下。<br/>
     * 返回条件为该格子的值不等于ComponentData.DATA_BLOCK。<br/>
     *
     * @param map    地图
     * @param target 终点
     * @return Point 终点最近的一个可行走点。当遍历过整个地图(遍历次数超过512)仍然没有合适的点时，返回null。
     */
    private Point findPointCanMoveTo(int[][] map, Point target) {
        //第一步：确定检查范围和顺序
        int len = 8;             //第一次遍历终点周围8格
        int times = 1;           //遍历次数
        int[] bef = new int[len]; //遍历的范围
        while (times < 513) { //地图最大512×340
            bef[0] = getPaneSingle(map, target.x - times, target.y - times);//第一个元素为终点左上格
            if (bef[0] != Constants.SOLID) {
                return new Point(target.x - times, target.y - times);
            }
            bef[1] = getPaneSingle(map, target.x, target.y - times);//第二个元素为终点正上格
            if (bef[1] != Constants.SOLID) {
                return new Point(target.x, target.y - times);
            }
            bef[2] = getPaneSingle(map, target.x + times, target.y - times);//第三个元素为终点右上格
            if (bef[2] != Constants.SOLID) {
                return new Point(target.x + times, target.y - times);
            }
            bef[3] = getPaneSingle(map, target.x - times, target.y);//第四个元素为终点正左格
            if (bef[3] != Constants.SOLID) {
                return new Point(target.x - times, target.y);
            }
            bef[4] = getPaneSingle(map, target.x + times, target.y);//第五个元素为终点正右格
            if (bef[4] != Constants.SOLID) {
                return new Point(target.x + times, target.y);
            }
            bef[5] = getPaneSingle(map, target.x - times, target.y + times);//第一个元素为终点左上格
            if (bef[5] != Constants.SOLID) {
                return new Point(target.x - times, target.y + times);
            }
            bef[6] = getPaneSingle(map, target.x, target.y + times);//第一个元素为终点左上格
            if (bef[6] != Constants.SOLID) {
                return new Point(target.x, target.y + times);
            }
            bef[7] = getPaneSingle(map, target.x + times, target.y + times);//第一个元素为终点左上格
            if (bef[7] != Constants.SOLID) {
                return new Point(target.x + times, target.y + times);
            }
            times++;
        }
        return null;
    }

    /**
     * 取得地图上(x,y)位置可行走设置
     *
     * @param map 地图
     * @param x   地图数组第一维下标x
     * @param y   地图数组第二维下标y
     * @return int 地图map在(x,y)位置上的可行走状态
     */
    private int getPaneSingle(int[][] map, int x, int y) {
        if (null != map) {
            try {
                return map[y][x];
            } catch (Exception e) {
//				log.debug("地图越界了....");
            }
        }
        return Constants.SOLID;
    }

    /**
     * 获取起点start到终点target之间的移动方向，默认为0(向上)<br/>
     * 该方法用于移动中每一步的方向获取，起点和终点之间应该是一步的距离<br/>
     *
     * @param start  起点
     * @param target 终点
     * @return int 方向值
     */
    private int getDirection(Node start, Node target) {
        int dx = target.location.x - start.location.x;
        int dy = target.location.y - start.location.y;
        if (dx == 0 && dy < 0) return Pane.DIRECT_UP;
        if (dx == 0 && dy > 0) return Pane.DIRECT_DOWN;
        if (dx > 0 && dy == 0) return Pane.DIRECT_RIGHT;
        if (dx < 0 && dy == 0) return Pane.DIRECT_LEFT;
        if (dx > 0 && dy > 0) return Pane.DIRECT_DOWN_RIGHT;
        if (dx > 0 && dy < 0) return Pane.DIRECT_UP_RIGHT;
        if (dx < 0 && dy > 0) return Pane.DIRECT_DOWN_LEFT;
        if (dx < 0 && dy < 0) return Pane.DIRECT_UP_LEFT;
        return 0;
    }


}

